package com.gitee.wsl.time.timer

import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.transformWhile
import kotlin.time.Duration
import com.gitee.wsl.flow.transform.firstInstance

/** A timer ticking a certain [duration]. */
interface StateTimer {
    /** Timer [Duration]. */
    val duration: Duration

    /** Flow of [State] of the timer. */
    val state: Flow<State>

    /** The current [State] of the timer. */
    val currentState: State

    /**
     * The state of a [Timer]
     */
    sealed interface State {
        /**
         * A [Flow] of the [Duration] that has elapsed while the timer was in a [Running] state
         */
        val elapsed: Flow<Duration>

        /**
         * A [State] that indicates the [Timer] is currently running. This may result in changes to [elapsed]
         */
        interface Running : State

        /**
         * A [State] that indicates that a [Timer] is not currently running.
         */
        sealed interface NotRunning : State {

            /**
             * A [NotRunning] state to indicate that the [Timer] has been paused
             */
            interface Paused : NotRunning

            /**
             * A [NotRunning] state to indicate that the [Timer] has finished running.
             */
            interface Finished : NotRunning
        }
    }
}

/** A [Timer] ticking a certain [duration] with the ability to [start], [pause] and [stop]. */
interface ControllableTimer : StateTimer {
    /**
     * Starts the timer.
     * @return `true` if the timer as started successfully, `false` otherwise.
     * */
    suspend fun start(): Boolean

    /**
     * Pauses the timer. Calling [start] again will make it resume.
     * @return `true` if the timer as paused successfully, `false` otherwise.
     * */
    suspend fun pause(): Boolean

    /**
     * Stops the timer causing it to finish. Calling [start] again will return `false`.
     * */
    suspend fun stop()
}

/** [Duration] that has elapsed while [Timer.state] was [Timer.State.Running]. */
@OptIn(ExperimentalCoroutinesApi::class)
fun StateTimer.elapsed(): Flow<Duration> = state
    .transformWhile { stateValue ->
        // [Finished] is the final state, ensure the consumer's [collect] would be able to exit
        emit(stateValue)
        stateValue !is StateTimer.State.NotRunning.Finished
    }
    .flatMapLatest { state -> state.elapsed }

/** Awaits for the [Timer] to reach the [Timer.State.NotRunning.Finished] state. */
suspend fun StateTimer.awaitFinish() {
    state.firstInstance<StateTimer.State.NotRunning.Finished>()
}
